package com.gitee.service.openapi;

import com.gitee.gateway.core.exception.BadWordException;
import com.gitee.gateway.core.exception.IpBlockException;
import io.github.resilience4j.ratelimiter.RequestNotPermitted;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.HttpException;
import io.vertx.ext.web.openapi.RouterBuilder;
import io.vertx.ext.web.validation.BodyProcessorException;
import io.vertx.ext.web.validation.ParameterProcessorException;
import io.vertx.ext.web.validation.RequestPredicateException;
import io.vertx.serviceproxy.ServiceException;
import org.jdbi.v3.core.JdbiException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.SQLException;
import java.util.Map;

/**
 * 实现 operation 的路由
 * 根据不同的 operationId 来调用不同的 handler
 */
class OperationsRouter {

    private final static Logger LOGGER = LoggerFactory.getLogger(OperationsRouter.class);
    private final static Map<Integer, String> _error = Map.of(
            400, "Bad Request",
            401, "Unauthorized",
            403, "Forbidden",
            404, "Not Found",
            413, "Request Entity Too Large",
            415, "Unsupported Media Type",
            500, "Internal Server Error",
            502, "Bad Gateway",
            503, "Service Unavailable",
            504, "Gateway Timeout"
    );

    public static Router build(RouterBuilder routerBuilder, OpenApiHandler openApiHandler) {
        var handlers = new Handlers(routerBuilder, openApiHandler);
        routerBuilder.operations().forEach(operation -> {
            String operationId = operation.getOperationId();
            if (operationId != null) {
                String[] parts = operationId.split("\\.");
                String action = parts.length > 1 ? parts[1] : null;
                operation.handler(handlers.handle(parts[0], action)).failureHandler(OperationsRouter::error);
            }
        });
        return routerBuilder.createRouter();
    }

    /**
     * operation 失败处理
     *
     * @param context 请求上下文
     */
    public static void error(RoutingContext context) {
        int code;
        String message;
        Throwable error = context.failure();
        switch (error) {
            case null -> {
                code = context.statusCode();
                message = _error.get(code);
            }
            case BadWordException ignored -> {
                code = HttpResponseStatus.FORBIDDEN.code();
                message = "Forbidden";
            }
            case IpBlockException ignored -> {
                code = HttpResponseStatus.FORBIDDEN.code();
                message = "Forbidden";
            }
            case OperationException oe -> {
                code = oe.getStatusCode();
                message = oe.getMessage();
            }
            case HttpException he -> {
                code = he.getStatusCode();
                message = _error.get(code);
            }
            case IllegalArgumentException iae -> {
                code = HttpResponseStatus.BAD_REQUEST.code();
                message = iae.getMessage();
            }
            case ParameterProcessorException ppe -> {
                code = HttpResponseStatus.BAD_REQUEST.code();
                message = ppe.getMessage();
            }
            case BodyProcessorException bpe -> {
                code = HttpResponseStatus.BAD_REQUEST.code();
                message = bpe.getMessage();
            }
            case RequestPredicateException rpe -> {
                code = HttpResponseStatus.BAD_REQUEST.code();
                message = rpe.getMessage();
            }
            case ServiceException se -> {
                code = se.failureCode();
                message = code > 0 ? se.getMessage() : "Service Failed";
                if (code < 0)
                    ERROR(context, message, se.getCause() != null ? se.getCause() : se);
            }
            case SQLException se -> {
                code = HttpResponseStatus.INTERNAL_SERVER_ERROR.code();
                message = "Database Failed";
                ERROR(context, message, se);
            }
            case JdbiException je -> {
                code = HttpResponseStatus.INTERNAL_SERVER_ERROR.code();
                message = "Database Failed";
                ERROR(context, message, je.getCause() != null ? je.getCause() : je);
            }
            case RequestNotPermitted ignored -> {
                code = HttpResponseStatus.TOO_MANY_REQUESTS.code();
                message = "Request Limited";
            }
            default -> {
                code = HttpResponseStatus.INTERNAL_SERVER_ERROR.code();
                message = "Operation Failed";
                ERROR(context, message, error);
            }
        }

        if (!context.response().headWritten()) {
            if (code <= 0)
                code = HttpResponseStatus.INTERNAL_SERVER_ERROR.code();
            context.response()
                    .setStatusCode(code)
                    .putHeader(HttpHeaders.CONTENT_TYPE, "application/json")
                    .end(new JsonObject().put("error", code).put("message", message).encode());
        }
    }

    /**
     * Critical error handling
     *
     * @param context 请求上下文
     * @param subject 日志标题
     * @param t       异常信息
     */
    private static void ERROR(RoutingContext context, String subject, Throwable t) {
        String message = "%s <- %s - \"%s %s\"".formatted(subject,
                context.request().remoteAddress().hostAddress(),
                context.request().method().name(),
                context.request().uri());
        LOGGER.error(message, t);
    }

}
